package org.apache.derby.iapi.util;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

/**
 * This class is a combination of two Data Structures, HashMap and Properties.
 * 
 * It was developed by students of Computer Science at UFCG (Federal University
 * of Campina Grande - Brazil) to replace the depreciated Properties Structure
 * to Sun's HashMap Structure.
 * 
 * This class will be replaced at most calls of Properties' methods.
 */
public class PropertiesMap {

	private HashMap hashMap;
	protected PropertiesMap defaults;


	public PropertiesMap() {
		hashMap = new HashMap();
	}
	
	public PropertiesMap(PropertiesMap defaults) {
		// TODO
		this.defaults = defaults;
	} 

	/**
	 * Cleans the Map, leaving it without any keys.
	 */
	public void clear() {
		hashMap.clear();
	}

	/**
	 * Tests if one key has one specified value in this map, this operation has
	 * a higher cost than containsKey method.
	 * 
	 * @param element
	 *            Elemento que se quer saber se esta contido.
	 * @return true se o valor estives contido, false se nao estiver. False se a
	 *         chave for passada.
	 */
	public boolean contains(Object element) {
		return hashMap.containsValue(element);
	}

	/**
	 * Tests if one key has one specified value in this map, this operation has
	 * a higher cost than containsKey method.
	 * 
	 * @param element
	 *            O valor que se quer saber se esta contido.
	 * @return true se o valor estiver contido, false se nao estiver.
	 */
	public boolean containsValue(Object element) {
		return hashMap.containsValue(element);
	}

	/**
	 * Tests if an specified object is a key in this map.
	 * 
	 * @param element
	 *            A chave que se quer saber se esta contida.
	 * @return true se a chave estiver contida, false se nao estiver.
	 */
	public boolean containsKey(Object element) {
		return hashMap.containsKey(element);
	}

	/**
	 * Returns a iterator of values of this map. Use Iterator's methods to
	 * handle this elements in sequence. The original method returns a
	 * enumeration, that behaves like an Iterator.
	 * 
	 * @return A iterator with all values contained in this map.
	 */
	public Iterator elements() {
		Collection col = hashMap.values();
		return col.iterator();
	}

	/**
	 * Returns a value linked by the specified key, or "null" if the map doesn't
	 * caontains the key.
	 * 
	 * @param key
	 *            - The key that you want to know the value.
	 * @return The value linked by the key.
	 */
	public Object get(String chave) {
		return hashMap.get(chave);
	}

	/**
	 * Search for the value linked by the specified key.
	 * 
	 * @param key
	 *            The key that you want to know the value.
	 * @return The value of the key, or "null" if the key is not found.
	 */
	public String getProperty(String chave) {
		return ((String) hashMap.get(chave));
	}

	/**
	 * Search for the value with the specified key. If the key is not found in
	 * the map, then returns the default value provided.
	 * 
	 * @param key
	 *            - The key that you want to know the value.
	 * @param defaultValue
	 *            - One default value to be returned if the key doesn't exists.
	 * @return The value of the key, or the default value
	 */
	public String getProperty(String chave, String defaultValue) {
		if (hashMap.get(chave) == null)
			return defaultValue;

		return (String) hashMap.get(chave);
	}

	/**
	 * Tests if this HashMap has no values.
	 * 
	 * @return true - if it is empty, false if it isn't empty.
	 */
	public boolean isEmpty() {
		return hashMap.isEmpty();
	}

	/**
	 * Returns an Iterator of keys in this HashMap. The original method in
	 * Properties Class returns an Enumeration, that behaves like a Iterator.
	 * 
	 * @return An Iterator<String> of keys.
	 */
	public Iterator keys() {
		Set cojunto = hashMap.keySet();
		return cojunto.iterator();
	}

	/**
	 * Prints this HashMap in the OutputStream given in a human readable form.
	 * 
	 * @param the
	 *            PrintStream to write the content to in human readable form.
	 */
	public void list(PrintStream out) {
		String cabecalho = "-- listing properties --\n";
		out.print(cabecalho.toCharArray());
		Iterator itHashMap = hashMap.keySet().iterator();
		String temp = new String();
		String tempItHashMap = new String();

		while (itHashMap.hasNext()) {
			tempItHashMap = (String) itHashMap.next();
			temp = tempItHashMap + "=" + hashMap.get(tempItHashMap) + "\n";
			out.print(temp.toCharArray());
		}
	}

	/**
	 * Prints this HashMap in the PrintWriter given in a human readable form.
	 * 
	 * @param the
	 *            PrintWriter to write the content to in human readable form.
	 */
	public void list(PrintWriter out) {
		final String lineSeparator = System.getProperty("line.separator");
		String cabecalho = "-- listing properties --" + lineSeparator;

		out.print(cabecalho.toCharArray());
		Iterator itHashMap = hashMap.keySet().iterator();
		String temp = new String();
		String tempItHashMap = new String();

		while (itHashMap.hasNext()) {
			tempItHashMap = (String) itHashMap.next();
			temp = tempItHashMap + "=" + hashMap.get(tempItHashMap)
					+ lineSeparator;
			out.print(temp.toCharArray());
		}
	}

	/**
	 * Reads a list of pairs of keys and values from a input byte stream.
	 * Assumes the use of ISO 8859-1 encoding, Ler uma lista de pares de chaves
	 * e valores a partir de um input byte stream. Assume o uso do ISO 8859-1
	 * encoding.
	 * 
	 * The specified Stream stays open after the return of this method.
	 * 
	 * @param inputStream
	 *            The InputStream
	 * @throws IOException
	 *             If an error occurs while the inputStream is being read.
	 */
	public synchronized void load(InputStream inputStream) throws IOException {
		if (inputStream == null) {
			throw new NullPointerException();
		}
		BufferedInputStream bis = new BufferedInputStream(inputStream);

		load(new InputStreamReader(bis, "ISO8859-1"));
	}

	/**
	 * Like the load(InputStream inputStream). For more information see
	 * Properties Documentation at JAVA 6 API.
	 * 
	 * @param reader
	 *            A InputStream of characters.
	 * @throws IOException
	 *             If an error occurs while the InputStream is being read.
	 */
	public synchronized void load(Reader reader) throws IOException {
		final String lineSeparator = System.getProperty("line.separator");
		BufferedReader bufferedReader = new BufferedReader(reader);
		int value = bufferedReader.read();
		String content = new String();

		while (value != -1) {
			content += (char) value;
			value = bufferedReader.read();
		}

		String[] contentSplit = content.split(lineSeparator);
		String key = new String();
		String valueOfKey = new String();
		String temp = new String();
		char[] lineChar = null;
		
		for(int j = 0; j < contentSplit.length; j++) {
			String lineString = contentSplit[j];
			
			if (!lineString.startsWith("#")) {
				lineChar = lineString.toCharArray();

				for (int i = 0; i < lineChar.length; i++) {
					if (lineChar[i] == '=' && lineChar[i - 1] != '\\') {
						key = getRightString(temp.toCharArray());
						temp = new String();
						continue;
					}

					temp += lineChar[i];
				}
				valueOfKey = getRightString(temp.toCharArray());
				temp = new String();
				hashMap.put(key, valueOfKey);
			}
		}
	}

	/**
	 * Returns an Iterator of all keys in this HashMap.
	 * 
	 * @return An Iterator with all the keys contained in this HashMap.
	 */
	public Iterator propertyNames() {
		return hashMap.keySet().iterator();
	}

	/**
	 * Maps a key for the specified value in this HashMap. The key and the value
	 * cannot be "null".
	 * 
	 * @param key
	 *            The specified key.
	 * @param value
	 *            The specified value.
	 * @return The previous value of the specified key, or "null" if the key
	 *         still doesn't exists.
	 */
	public Object put(Object chave, Object valor) {
		if (chave == null || valor == null)
			throw new NullPointerException();
		return hashMap.put(chave, valor);
	}

	/**
	 * Removes the key (and your correspondent value) in the HashMap. This
	 * method doesn't do anything if the map doesn't contains this key.
	 * 
	 * @param key
	 *            The key that will be removed.
	 * @return The value correspondent to the key in the HashMap, or "null" if
	 *         the key doesn't exists in the map.
	 */
	public Object remove(Object key) {
		return hashMap.remove(key);
	}

	/**
	 * Like "put" of HashTable, but it only receives String. Modifying or adding
	 * one key with its specified value.
	 * 
	 * @param key
	 *            The key to be put in the map.
	 * @param value
	 *            The correpondent value of the key.
	 * @return The previous value of the specified key in the Map, or "null" if
	 *         the key doesn't exists.
	 */
	public Object setProperty(String key, String value) {
		return put(key, value);
	}

	/**
	 * Returns the number of keys in the HashMap.
	 * 
	 * @return The number of keys in the HashMap.
	 */
	public int size() {
		return hashMap.size();
	}

	/**
	 * Writes the pairs of keys and values in the specified output Stream. For
	 * more information about the behavior of this method, see the Properties
	 * Documentation at JAVA 6 API.
	 * 
	 * The output Stream stills open after the return of this method.
	 * 
	 * @param out
	 *            The Output Stream ready to be writen.
	 * @param comment
	 *            A description of pairs key-value.
	 * 
	 * @throws IOException
	 *             If during the write in the Output Stream, throw a
	 *             IOException.
	 */
	public synchronized void store(OutputStream out, String comment)
			throws IOException {
		final String lineSeparator = System.getProperty("line.separator");
		OutputStreamWriter writer = new OutputStreamWriter(out, "ISO8859_1");
		if (comment != null) {
			writer.write("#");
			writer.write(comment);
			writer.write(lineSeparator);
		}
		writer.write("#");
		writer.write(new Date().toString());
		writer.write(lineSeparator);

		Iterator itHashMap = hashMap.keySet().iterator();
		String itTemp = new String();
		String temp = new String();

		while (itHashMap.hasNext()) {
			itTemp = (String) itHashMap.next();
			temp = setRightString(itTemp.toCharArray()) + "="
					+ setRightString(((String) hashMap.get(itTemp)).toCharArray())
					+ lineSeparator;
			writer.write(temp);
		}

		writer.flush();
	}

	/**
	 * Writes the pairs of keys and values in the specified output Stream. For
	 * more information about the behavior of this method, see the Properties
	 * Documentation at JAVA 6 API.
	 * 
	 * The output Stream stills open after the return of this method.
	 * 
	 * @param out
	 *            The Output Stream ready to be writen.
	 * @param comment
	 *            A description of pairs key-value.
	 * 
	 * @throws IOException
	 *             If during the write in the Output Stream, throw a
	 *             IOException.
	 */
	public synchronized void store(Writer writer, String comment)
			throws IOException {
		final String lineSeparator = System.getProperty("line.separator");
		if (comment != null) {
			writer.write("#");
			writer.write(comment);
			writer.write(lineSeparator);
		}
		writer.write("#");
		writer.write(new Date().toString());
		writer.write(lineSeparator);

		Iterator itHashMap = hashMap.keySet().iterator();
		String itTemp = new String();
		String temp = new String();

		while (itHashMap.hasNext()) {
			itTemp = (String) itHashMap.next();
			temp = setRightString(itTemp.toCharArray()) + "="
					+ setRightString(((String) hashMap.get(itTemp)).toCharArray())
					+ lineSeparator;
			writer.write(temp);
		}

		writer.flush();
	}

	public String toString() {
		String out = "{";
		String key = new String();
		Iterator itHashMap = hashMap.keySet().iterator();
		while (itHashMap.hasNext()) {
			key = (String) itHashMap.next();
			out += key + "=" + hashMap.get(key) + ", ";
		}

		if (hashMap.size() > 0) {
			out = out.trim().subSequence(0, out.length() - 2) + "}";
		} else {
			out += "}";
		}

		return out;
	}

	/**
	 * Takes a String without the correct formatting that is necessary for the
	 * storage of the string. Returns a string in the way that it was given to
	 * the HashMap.
	 * 
	 * @param word
	 *            An array of characters of the word.
	 * @return The word without unnecessary characters.
	 */
	private String getRightString(char[] word) {
		String temp = new String();

		for (int i = 0; i < word.length; i++) {
			if (word[i] == '\\') {
				switch (word[i + 1]) {
				case 't':
					temp += '\t';
					i++;
					break;
				case 'n':
					temp += '\n';
					i++;
					break;
				case 'f':
					temp += '\f';
					i++;
					break;
				case 'r':
					temp += '\r';
					i++;
					break;
				case '=':
					temp += '=';
					i++;
					break;
				default:
					temp += word[i];
				}
			} else {
				temp += word[i];
			}
		}

		return temp;
	}

	/**
	 * Formats the string for the format that must be storage, for not having
	 * loss of information in time to rescue the pairs key-value. This
	 * formatting serves not to lose values like "\n", "\r", "\t"...
	 * 
	 * @param word
	 *            An array of characters with the word to be formatted.
	 * @return The word with proper formatting.
	 */
	private String setRightString(char[] word) {
		String temp = new String();
		for(int i = 0; i < word.length; i++ ) {
			char character = word[i];
		//for (char character : word) {
			switch (character) {
			case '\t':
				temp += "\\t";
				break;
			case '\n':
				temp += "\\n";
				break;
			case '\f':
				temp += "\\f";
				break;
			case '\r':
				temp += "\\r";
				break;
			default:
				if ("\\#!=:".indexOf(character) >= 0 || (character == ' ')) {
					temp += "\\";
				}
				if (character >= ' ' && character <= '~') {
					temp += character;
				} else {
					String hex = Integer.toHexString(character);
					temp += "\\u";
					for (int j = 0; j < 4 - hex.length(); j++) {
						temp += "0";
					}
					temp += hex;
				}

			}
		}

		return temp;
	}
	
	public Object clone() {
		Iterator itKeys = hashMap.keySet().iterator();
		PropertiesMap pmfd = new PropertiesMap(); 
		
		while(itKeys.hasNext()) {
			String key = (String) itKeys.next();
			pmfd.setProperty(key, ((String) hashMap.get(key)));
			
		}
		return pmfd;
	}
	
	public PropertiesMapFromDictionary getPropertiesMapFromDictionary() {
		Iterator itKeys = hashMap.keySet().iterator();
		PropertiesMapFromDictionary pmfd = new PropertiesMapFromDictionary(); 
		
		while(itKeys.hasNext()) {
			String key = (String) itKeys.next();
			pmfd.setProperty(key, ((String) hashMap.get(key)));
			
		}
		return pmfd;
	}
}

